/**
 * @file bull_hal_ping_network.c
 * @brief 
 * @author yangyl
 * @version 1.0
 * @date 2025-01-02
 * 
 * Copyright 2021-2025 GongNiu Inc. All Rights Reserved.
 * 
 */
#include "bull_hal_ping_network.h"
#include "bull_hal_drv_wifi.h"
#include "bull_hal_sys_thread.h"
#include "bull_hal_sys_memory.h"
#include "bull_hal_sys_system.h"

#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "ping/ping_sock.h"
#include "esp_wifi_types.h"
#include "nvs_flash.h"

#include <sys/types.h>
#include <sys/socket.h>
#include "esp_log.h"

#define PING_ADDR "g8ujUzdT8rD.iot-as-mqtt.cn-shanghai.aliyuncs.com"

static ping_result_cb bull_ping_result_cb = NULL;

#define TAG "ping"

#define PING_CNT 60
static uint8_t ping_succuss_cnt = 0;
static uint32_t ping_max_delay = 0;
static uint32_t ping_all_delay = 0;

static uint8_t ping_ttl_flag = 0; //为1表示正在ping期间标志

static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
{
    uint8_t ttl;
    uint16_t seqno;
    uint32_t elapsed_time, recv_len;
    ip_addr_t target_addr;
    esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
    esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
    esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
    esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
    esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
    ESP_LOGW(TAG, "%" PRIu32 " bytes from %s icmp_seq=%" PRIu16 " ttl=%" PRIu16 " time=%" PRIu32 " ms\n",
           recv_len, ipaddr_ntoa((ip_addr_t*)&target_addr), seqno, ttl, elapsed_time);

    ping_succuss_cnt++;
    ping_all_delay += elapsed_time;     
    if(ping_max_delay < elapsed_time){
        ping_max_delay = elapsed_time;
    }        
}

static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
    uint16_t seqno;
    ip_addr_t target_addr;
    esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
    esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
    ESP_LOGW(TAG,"From %s icmp_seq=%d timeout\n",ipaddr_ntoa((ip_addr_t*)&target_addr), seqno);
}


static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
{
    ip_addr_t target_addr;
    uint32_t transmitted;
    uint32_t received;
    uint32_t total_time_ms;
    uint32_t loss;

    esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
    esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
    esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
    esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));


    if (ping_succuss_cnt > 0) {

        if(ping_succuss_cnt > PING_CNT) // the ping_succuss_cnt may be larger than PING_CNT
            ping_succuss_cnt = PING_CNT; 

        loss = (uint32_t)((1 - ((float)ping_succuss_cnt) / PING_CNT) * 100);
    } else {
        loss = 0;
    }

    if (IP_IS_V4(&target_addr)) {
        ESP_LOGI(TAG,"\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
    } else {
        ESP_LOGI(TAG, "\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
    }
    // ESP_LOGW(TAG, "%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n",
    //        transmitted, received, loss, total_time_ms);
    ping_ttl_flag = 0;
    if(bull_ping_result_cb){
        bull_ping_result_cb(loss, ping_all_delay/ping_succuss_cnt, ping_max_delay);
    }
    // delete the ping sessions, so that we clean up all resources and can create a new ping session
    // we don't have to call delete function in the callback, instead we can call delete function from other tasks
    esp_ping_delete_session(hdl);
}


void bull_hal_ping_net(ping_result_cb ping_result_cb_func)
{
    // parse IP address
    struct sockaddr_in6 sock_addr6;
    ip_addr_t target_addr;
    memset(&target_addr, 0, sizeof(target_addr));
    
    if(ping_ttl_flag == 1){
        ESP_LOGW(TAG,"The current ping is not complete, exit");
        return;
    }

    bull_ping_result_cb = ping_result_cb_func;
    ping_succuss_cnt = 0;
    ping_max_delay = 0;
    ping_all_delay = 0;
    if (inet_pton(AF_INET6, PING_ADDR, &sock_addr6.sin6_addr) == 1) {
        /* convert ip6 string to ip6 address */
        ipaddr_aton(PING_ADDR, &target_addr);
    } else {
        struct addrinfo hint;
        struct addrinfo *res = NULL;
        memset(&hint, 0, sizeof(hint));
        /* convert ip4 string or hostname to ip4 or ip6 address */
        if (getaddrinfo(PING_ADDR, NULL, &hint, &res) != 0) {
            ESP_LOGE(TAG,"ping fail! %s\n", PING_ADDR);
            ping_ttl_flag = 0;
            bull_ping_result_cb(101, 0, 0);
            return;
        }
        if (res->ai_family == AF_INET) {
            struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
            inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
        } else {
            struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
            inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
        }
        freeaddrinfo(res);
    }

    esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
    ping_config.target_addr = target_addr;          // target IP address
    
    ping_config.timeout_ms = 1000;
    ping_config.interval_ms = 1000;
    ping_config.count = PING_CNT; // ESP_PING_COUNT_INFINITE // ping in infinite mode, esp_ping_stop can stop it

    /* set callback functions */
    esp_ping_callbacks_t cbs;
    cbs.on_ping_success = cmd_ping_on_ping_success;
    cbs.on_ping_timeout = cmd_ping_on_ping_timeout;
    cbs.on_ping_end = cmd_ping_on_ping_end;
    cbs.cb_args = NULL;  // arguments that will feed to all callback functions, can be NULL

    esp_ping_handle_t ping;
    if (esp_ping_new_session(&ping_config, &cbs, &ping) == ESP_OK) {
        ESP_LOGW(TAG,"Pinging Peer with IPv6 addr %s", ipaddr_ntoa((ip_addr_t *)&target_addr));
        ping_ttl_flag = 1;
        esp_ping_start(ping);
    } else {
        ESP_LOGW(TAG,"Failed to ping Peer with IPv6 addr %s", ipaddr_ntoa((ip_addr_t *)&target_addr));
    }
}